/*
 * TowerFireControl.java
 *
 * Created on January 5, 2008, 4:10 PM
 *
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package hextd.tower;

import hextd.hex.Hex;
import hextd.sprite.EnemySprite;
import hextd.util.Cache;
import hextd.util.Context;
import hextd.util.Geometry;
import hextd.util.TowerListener;
import hextd.util.WaveStartListener;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;

/**
 *
 * @author hylke
 */
public class TowerFireControl extends TowerBase implements TowerListener, WaveStartListener {
    
    public static int price = 0;
    public static int damage = 0;
    public static float range = 2.5f;
    
    private Color           line2Color;
    private Color           transColor;
    
    private int mode1 = 0;
    private int mode2 = 0;
    
    private Vector<Tower>   clients;
    private Line2D[]        clientLines;
    private Stroke          clientStroke;
    private Color           clientColor;
    
    private Hashtable<Tower, EnemyData> targetTable;
    private EnemyData[]         enemyData;
    private CompareCloseSame    compCloseSame = new CompareCloseSame();
    private CompareCloseDiff    compCloseDiff = new CompareCloseDiff();
    private CompareHardSame     compHardSame  = new CompareHardSame();
    private CompareHardDiff     compHardDiff  = new CompareHardDiff();
    private CompareWeakSame     compWeakSame  = new CompareWeakSame();
    private CompareWeakDiff     compWeakDiff  = new CompareWeakDiff();
    private CompareFarSame      compFarSame  = new CompareFarSame();
    private CompareFarDiff      compFarDiff  = new CompareFarDiff();
    String[] modes2 = {"Same", "Diff", "Diff++"};
    String[] modes1 = {"Close", "Hard", "Weak", "Furthest"};
    
    private class EnemyData {
        public EnemySprite  enemy;
        public float        dist2;
        public float        health;
        public float        progress;
        public int          towerCount;
        public int          nr;
        public EnemyData(EnemySprite e, int i) {
            this.enemy = e;
            this.nr = i;
        }
    }
    private class CompareCloseSame implements Comparator<EnemyData> {
        public int compare(EnemyData e1, EnemyData e2) {
            if (e1.towerCount != e2.towerCount) {
                return e2.towerCount - e1.towerCount;
            } else {
                return (int)(e1.dist2 - e2.dist2);
            }
        }
    }
    private class CompareCloseDiff implements Comparator<EnemyData> {
        public int compare(EnemyData e1, EnemyData e2) {
            if (e1.towerCount != e2.towerCount) {
                return e1.towerCount - e2.towerCount;
            } else {
                return (int)(e1.dist2 - e2.dist2);
            }
        }
    }
    private class CompareHardSame implements Comparator<EnemyData> {
        public int compare(EnemyData e1, EnemyData e2) {
            if (e1.towerCount != e2.towerCount) {
                return e2.towerCount - e1.towerCount;
            } else {
                return (int)(e2.health - e1.health);
            }
        }
    }
    private class CompareHardDiff implements Comparator<EnemyData> {
        public int compare(EnemyData e1, EnemyData e2) {
            if (e1.towerCount != e2.towerCount) {
                return e1.towerCount - e2.towerCount;
            } else {
                return (int)(e2.health - e1.health);
            }
        }
    }
    private class CompareWeakSame implements Comparator<EnemyData> {
        public int compare(EnemyData e1, EnemyData e2) {
            if (e1.towerCount != e2.towerCount) {
                return e2.towerCount - e1.towerCount;
            } else {
                return (int)(e1.health - e2.health);
            }
        }
    }
    private class CompareWeakDiff implements Comparator<EnemyData> {
        public int compare(EnemyData e1, EnemyData e2) {
            if (e1.towerCount != e2.towerCount) {
                return e1.towerCount - e2.towerCount;
            } else {
                return (int)(e1.health - e2.health);
            }
        }
    }
    private class CompareFarSame implements Comparator<EnemyData> {
        public int compare(EnemyData e1, EnemyData e2) {
            if (e1.towerCount != e2.towerCount) {
                return e2.towerCount - e1.towerCount;
            } else {
                return (int)((e2.progress - e1.progress)*1000);
            }
        }
    }
    private class CompareFarDiff implements Comparator<EnemyData> {
        public int compare(EnemyData e1, EnemyData e2) {
            if (e1.towerCount != e2.towerCount) {
                return e1.towerCount - e2.towerCount;
            } else {
                return (int)((e2.progress - e1.progress)*1000);
            }
        }
    }
    
    /** Creates a new instance of TowerFireControl */
    public TowerFireControl(Context context, Hex hex) {
        super(TowerFactory.type.firecontrol, price, damage, range);
        this.lineColor = new Color(200, 200, 200);
        this.line2Color = new Color(255, 200, 200);
        this.clientColor = new Color(255, 200, 200);
        this.transColor = new Color(this.lineColor.getRed(), this.lineColor.getGreen(), this.lineColor.getBlue(), 100);
        this.clients = new Vector<Tower>();
        
        this.doInit(context, hex);
        
        this.context.addTowerListener(this);
        this.context.addWaveStartListener(this);
        this.targetTable = new Hashtable<Tower, EnemyData>();
        this.enemyData = new EnemyData[0];
        
        this.scanTowers();
        this.waveStarted();
    }
    
    protected void doInit(Context context, Hex hex) {
        this.context = context;
        this.rangeColor = this.context.colors.getColorRangeCircle();
        this.damageCurrent = this.damageBase;
        
        this.hex = hex;
        if (hex != null) {
            this.hexLocation = hex.getLocation();
        }
        
        PanelTowerSettingsDouble p = new PanelTowerSettingsDouble(this, this.context);
        p.setModes1(this.modes1);
        p.setModes2(this.modes2);
        this.pts = p;
        this.resetScale();
    }
    
    private void scanTowers() {
        if (!this.selling) {
            Tower t;
            float dx,dy;
            float[] loc;
            for (Iterator<Tower> i=this.context.towers.iterator(); i.hasNext();) {
                t = i.next();
                if (!this.clients.contains(t)) {
                    switch (t.getType()) {
                        case firecontrol:
                        case upgradeDamage:
                        case upgradeRange:
                            break;
                        default:
                            loc = t.getHex().getFloatLocation();
                            dx = this.xFloat-loc[0];
                            dy = this.yFloat-loc[1];
                            if ( (dx*dx+dy*dy) < this.rangeCurrent2 ) {
                                t.registerTower(this);
                            }
                            break;
                    }
                } else {
                    loc = t.getHex().getFloatLocation();
                    dx = this.xFloat-loc[0];
                    dy = this.yFloat-loc[1];
                    if ( (dx*dx+dy*dy) > this.rangeCurrent2 ) {
                        t.unregisterTower(this);
                    }
                }
            }
        }
    }
    
    protected void calcDamageRange() {
        super.calcDamageRange();
        this.scanTowers();
    }
    
    public boolean canUpgrade() {
        return false;
    }
    
    public String getStatusString() {
        String retString = "";
        retString += "<h1>Fire Control</h1>" +
                "<p>" +
                "Controls the targeting of other towers<br>" +
                "<b>Towers controlled:</b> "+this.clients.size()+"<br>\n" +
                "<b>Range:</b> "+this.rangeText+"<br>" +
                "</p>" +
                "<p><b>R</b>: Next control mode</p>\n";
        return retString;
    }
    
    public String getInfoString() {
        String retString = "";
        retString += "<h1>Fire Control</h1>" +
                "<p>" +
                "This tower controls the targeting of other towers and can force them to fire at the same, or different targets. " +
                "It has no effect on Blue Towers 1 & 2."+
                "</p><p class='warn'>This tower costs 1 bonus point.</p>";
        return retString;
    }
    
    public void doTick(int gameTime) {
    }
    
    public void towerBuild(Tower t) {
        if (t != this && !this.selling && !this.clients.contains(t)) {
            switch (t.getType()) {
                case firecontrol:
                case upgradeDamage:
                case upgradeRange:
                    break;
                default:
                    float dx,dy;
                    float[] loc;
                    loc = t.getHex().getFloatLocation();
                    dx = this.xFloat-loc[0];
                    dy = this.yFloat-loc[1];
                    if ( (dx*dx+dy*dy) < this.rangeCurrent2 ) {
                        t.registerTower(this);
                    }
                    break;
            }
        }
    }
    
    public void towerRemoved(Tower t) {
        if (this.clients.contains(t)) {
            t.unregisterTower(this);
        }
    }
    
    public void addClient(Tower t) {
        if (!this.selling && !this.clients.contains(t)) {
            this.clients.add(t);
            this.generateShapes();
        }
    }
    public void removeClient(Tower t) {
        this.clients.remove(t);
        this.generateShapes();
    }
    
    public void generateShapes() {
        Cache cache = this.context.getCache();
        
        float width = this.context.hexPaintWidth;
        int shapeCount = 13;
        int i;
        
        float cx = +this.paintScale*1/4;
        float cy = -this.paintScale/5;
        float[][] points1 = Geometry.createPolyCoords(this.paintScale/2, cx, cy);
        float[][] points2 = Geometry.createPolyCoords(this.paintScale);
        
        i=0;
        this.shapes = new Shape[shapeCount];
        this.shapes[i++] = new Line2D.Float( cx,  cy, points1[0][0], points1[0][1]);
        this.shapes[i++] = new Line2D.Float( cx,  cy, points1[1][0], points1[1][1]);
        this.shapes[i++] = new Line2D.Float( cx,  cy, points1[2][0], points1[2][1]);
        this.shapes[i++] = new Line2D.Float( cx,  cy, points1[3][0], points1[3][1]);
        this.shapes[i++] = new Line2D.Float( cx,  cy, points1[4][0], points1[4][1]);
        this.shapes[i++] = new Line2D.Float( cx,  cy, points1[5][0], points1[5][1]);
        this.shapes[i++] = new Line2D.Float( cx,  cy, points2[0][0], points2[0][1]);
        this.shapes[i++] = new Line2D.Float( cx,  cy, points2[1][0], points2[1][1]);
        this.shapes[i++] = new Line2D.Float( cx,  cy, points2[2][0], points2[2][1]);
        this.shapes[i++] = new Line2D.Float( cx,  cy, points2[3][0], points2[3][1]);
        this.shapes[i++] = new Line2D.Float( cx,  cy, points2[5][0], points2[5][1]);
        this.shapes[i++] = Geometry.createCircle(cx,cy, this.paintScale/2);
        this.shapes[i++] = Geometry.createCircle(cx,cy, this.paintScale/2);
        
        this.shapeAction = new int[shapeCount];
        for (i=0; i<shapeCount; i++) {
            this.shapeAction[i] = 1;
        }
        this.shapeAction[11] = 0;
        
        this.shapeColor = new Color[shapeCount];
        for (i=0; i<6; i++) {
            this.shapeColor[i] = this.line2Color;
        }
        for (i=6; i<shapeCount; i++) {
            this.shapeColor[i] = this.lineColor;
        }
        this.shapeColor[11] = this.transColor;
        
        i=0;
        this.strokes = new Stroke[shapeCount];
        this.strokes[i++] = new BasicStroke(1.5f*this.context.strokeScale, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);
        this.strokes[i++] = this.strokes[0];
        this.strokes[i++] = this.strokes[0];
        this.strokes[i++] = this.strokes[0];
        this.strokes[i++] = this.strokes[0];
        this.strokes[i++] = this.strokes[0];
        this.strokes[i++] = new BasicStroke(1.0f*this.context.strokeScale, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);
        this.strokes[i++] = this.strokes[6];
        this.strokes[i++] = this.strokes[6];
        this.strokes[i++] = this.strokes[6];
        this.strokes[i++] = this.strokes[6];
        this.strokes[i++] = this.strokes[6];
        this.strokes[i++] = this.strokes[6];
        
        this.clientLines = new Line2D[this.clients.size()];
        float[] from;
        float[] to = this.getHex().getPaintLocation();
        to[0] += cx;
        to[1] += cy;
        for (i=0; i<this.clientLines.length; i++) {
            from = this.clients.get(i).getHex().getPaintLocation();
            this.clientLines[i] = new Line2D.Float(from[0], from[1], to[0], to[1]);
        }
        this.clientStroke = new BasicStroke(1.0f*this.context.strokeScale, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);
    }
    
    public void paintEffects(Graphics2D g2, int gameTime) {
        g2.setStroke(this.clientStroke);
        g2.setColor(this.clientColor);
        for (int i=0; i<this.clientLines.length; i++) {
            g2.draw(this.clientLines[i]);
        }
    }
    
    public void doCleanup() {
        super.doCleanup();
        for (int i=this.clients.size()-1; i>=0; i--) {
            Tower t = this.clients.get(i);
            t.unregisterTower(this);
        }
        this.context.removeTowerListener(this);
        this.context.removeWaveStartListener(this);
    }
    
    public void waveStarted() {
        if (this.context.enemies != null) {
            this.enemyData = new EnemyData[this.context.enemies.length];
            for (int i=0; i<this.context.enemies.length; i++) {
                this.enemyData[i] = new EnemyData(this.context.enemies[i], i);
            }
        } else {
            this.enemyData = new EnemyData[0];
        }
        this.targetTable.clear();
    }
    
    public boolean setMode(int m, int v) {
        if (m==1 && this.mode1 != v) {
            if (v >= this.modes1.length) {
                v = 0;
            }
            this.mode1 = v;
            
            this.retarget = true;
            System.out.println("TowerFireControl::setMode: Setting to "+this.modes1[this.mode1]);
            if (this.pts.getMode(m) != v) {
                this.pts.setMode(m, v);
            }
            return true;
        } else if (m==2 && this.mode2 != v) {
            if (v >= this.modes2.length) {
                v = 0;
            }
            this.mode2 = v;
            
            this.retarget = true;
            System.out.println("TowerFireControl::setMode: Setting to "+this.modes2[this.mode2]);
            if (this.pts.getMode(m) != v) {
                this.pts.setMode(m, v);
            }
            return true;
        } else if (m!=1 && m!=2) {
            System.err.println("TowerFireControl::setMode: unknown mode: "+m);
            return false;
        }
        return true;
    }
    public int getMode(int m) {
        if (m==1) {
            return this.mode1;
        } else if (m==2) {
            return this.mode2;
        } else {
            System.err.println("TowerBase::setMode: unknown mode: "+m);
            return -1;
        }
    }
    
    public void updateEnemies(float x, float y) {
        float dx,dy;
        for (int i=0; i<this.enemyData.length; i++) {
            dx = x-this.enemyData[i].enemy.getX();
            dy = y-this.enemyData[i].enemy.getY();
            enemyData[i].dist2 = dx*dx+dy*dy;
            enemyData[i].health = enemyData[i].enemy.getHealth();
            enemyData[i].progress = enemyData[i].enemy.getProgress();
        }
    }
    
    public int findEnemy(Tower t, int preferedEnemyNr, boolean exShielded) {
        if (this.retarget) {
            preferedEnemyNr = -1;
            this.retarget = false;
        }
        switch (this.mode1) {
            case 0:
                return this.findEnemyClosest(t, preferedEnemyNr, exShielded);
            case 1:
                return this.findEnemyHardest(t, preferedEnemyNr, exShielded);
            case 2:
                return this.findEnemyWeakest(t, preferedEnemyNr, exShielded);
            case 3:
                return this.findEnemyFurthest(t, preferedEnemyNr, exShielded);
            default:
                System.err.println("TowerFireControl::FindEnemy: Unknown mode1 "+this.mode1);
        }
        return -1;
    }
    
    private int findEnemyClosest(Tower t, int preferedEnemyNr, boolean exShielded) {
        return findEnemy(t, preferedEnemyNr, exShielded, this.compCloseSame, this.compCloseDiff);
    }
    private int findEnemyHardest(Tower t, int preferedEnemyNr, boolean exShielded) {
        return findEnemy(t, preferedEnemyNr, exShielded, this.compHardSame, this.compHardDiff);
    }
    private int findEnemyWeakest(Tower t, int preferedEnemyNr, boolean exShielded) {
        return findEnemy(t, preferedEnemyNr, exShielded, this.compWeakSame, this.compWeakDiff);
    }
    private int findEnemyFurthest(Tower t, int preferedEnemyNr, boolean exShielded) {
        return findEnemy(t, preferedEnemyNr, exShielded, this.compFarSame, this.compFarDiff);
    }
    
    private int findEnemy(Tower t, int preferedEnemyNr, boolean exShielded, Comparator<EnemyData> cSame, Comparator<EnemyData> cDiff) {
        float r2 = t.getRangeCurrent();
        r2 *= r2;
        float[] loc = t.getHex().getFloatLocation();
        this.updateEnemies(loc[0], loc[1]);
        
        boolean force = false;
        EnemySprite e;
        EnemyData ed, oldEd;
        oldEd = this.targetTable.get(t);
        
        switch (this.mode2) {
            case 0:
                Arrays.sort(this.enemyData, cSame);
                break;
            case 2:
                force = true;
            case 1:
                if ( (oldEd != null) && (oldEd.dist2 < r2) && (oldEd.towerCount==1) ) {
                    if ( oldEd.enemy.validTarget() && !(exShielded && oldEd.enemy.isShielded()) ) {
                        return oldEd.nr;
                    } else {
                        oldEd.towerCount--;
                        this.targetTable.remove(t);
                        oldEd = null;
                    }
                }
                Arrays.sort(this.enemyData, cDiff);
                break;
        }
        
        for(int i=0; i<this.enemyData.length; i++) {
            ed = this.enemyData[i];
            e = ed.enemy;
            if ( e.validTarget() && !(exShielded && e.isShielded()) ) {
                if (ed.dist2 < r2 && !(force && ed.towerCount>0)) {
                    // this is our target!
                    if (ed != oldEd) {
                        // we changed targets!
                        if (oldEd != null) {
                            oldEd.towerCount--;
                        }
                        ed.towerCount++;
                        this.targetTable.put(t, ed);
                    }
                    return ed.nr;
                }
            }
        }
        if (oldEd != null) {
            oldEd.towerCount--;
            this.targetTable.remove(t);
        }
        return -1;
    }
    
}
